import struct as _struct
from lib import int_to_bin, bin_to_int, swap_bytes, OnDemandContainer
from core import *


#===============================================================================
# exceptions
#===============================================================================
class TerminatorError(ConstructError):
    pass
class PaddingError(ConstructError):
    pass


#===============================================================================
# constructs
#===============================================================================
class FormatField(Field):
    """
    A field that is parsed/built by a format-string (using the built-in struct 
    module). Note that the data must be byte-aligned.
    
    Constructor:
    * name - the name of the construct
    * endianness - one of "<" (little), ">" (big), or "=" (native)
    * format_char - any of the format chars that the struct module supports; 
      only one formatter is allowed

    Example:
    FormatField("blah", ">", "H")
    """
    __slots__ = ["format"]
    def __init__(self, name, endianness, format_char):
        assert endianness in ("<", ">", "=")
        assert len(format_char) == 1, "only one format specifier allowed"
        self.format = endianness + format_char
        Field.__init__(self, name, _struct.calcsize(self.format))
    def __repr__(self):
        return "<%s(%r, %r)>" % (self.__class__.__name__, self.name, self.format)
    def _parse(self, stream, context):
        return _struct.unpack(self.format, Field._parse(self, stream, context))[0]
    def _build(self, obj, stream, context):
        stream.write(_struct.pack(self.format, obj))

class BitField(Field):
    """
    An bit-field holding an integer. Unlike FormatField, bit fields can be 
    unaligned and consist of any number of bits (not necessarily multiple of 8).
    
    Constructor:
    * name - the name of the construst
    * length - the length, in bits, of the field
    * signed - boolean indicating whether this is a signed-integer (using 
      two's complement); the default, it's unsigned.
    * swap - boolean indicating whether this is a little-endian integer (swaps
      all bytes); the default is unswapped (big-endian)
    * byte_size - the number of bits per byte, used for byte swapping; the 
      default is 8
    
    Example:
    BitField("blah", 7) # a 7-bit integer field
    """
    __slots__ = ["signed", "swap", "byte_size"]
    def __init__(self, name, length, signed = False, swap = False, byte_size = 8):
        Field.__init__(self, name, length)
        self.signed = signed
        self.swap = swap
        self.byte_size = byte_size
    def _parse(self, stream, context):
        raw = Field._parse(self, stream, context)
        if self.swap:
            raw = swap_bytes(raw, self.byte_size)
        return bin_to_int(raw, self.signed)
    def _build(self, obj, stream, context):
        raw = int_to_bin(obj, self.length)
        if self.swap:
            raw = swap_bytes(raw, self.byte_size)
        stream.write(raw)

class Padding(Field):
    """
    A padding field (its value is discarded)
    
    Constructor:
    * length - the length of the field
    * pattern - the padding pattern; the default is \\x00
    * ignore - boolean indicating whether to ignore invalid padding patterns;
      the default is True (do ignore)
    
    Example:
    Padding(3)
    """
    __slots__ = ["pattern", "ignore"]
    def __init__(self, length, pattern = "\x00", ignore = True):
        Field.__init__(self, None, length)
        self.pattern = pattern * length
        self.ignore = ignore
    def _parse(self, stream, context):
        data = Field._parse(self, stream, context)
        if not self.ignore and data != self.pattern:
            raise PaddingError("invalid pattern, expected %r, found %r" % 
                (self.pattern, data))
    def _build(self, obj, stream, context):
        stream.write(self.pattern)

class Terminator(Construct):
    """
    Asserts the end of the stream has been reached; if not, raises an error.
    This class has a single instance that shadows the class name; you are not
    supposed to create instances of this class by yourself.
    
    Example:
    Struct("blah",
        UBInt8("yada"),
        Terminator
    )
    """
    __slots__ = []
    def _build(self, obj, stream, context):
        pass
    def _parse(self, stream, context):
        if stream.read(1):
            stream.seek(-1, 1)
            raise TerminatorError("end of stream not reached")
    def _getsize(self):
        return 0
Terminator = Terminator(None)

class Pass(Construct):
    """
    A do-nothing construct. Useful as the default case for Switch constructs,
    etc. This class has a single instance that shadows the class name; you are 
    not supposed to create instances of this class by yourself.
    
    Example:
    Switch("blah", 
        lambda ctx : 5, 
        {
            1 : UBInt8("a"),
            3 : UBInt8("a"),
        },
        default = Pass
    )
    """
    __slots__ = []
    def _parse(self, stream, context):
        pass
    def _build(self, obj, stream, context):
        pass
    def _getsize(self):
        return 0
Pass = Pass(None)

class Rename(Subconstruct):
    """
    Renames a subconstruct. 
    
    Constructor:
    * name - the new name to use
    * subcon - the subconstruct to rename
    
    Example:
    Rename("blah", UBInt8("yada"))
    """
    __slots__ = []
    def __init__(self, name, subcon):
        Construct.__init__(self, name)
        self.subcon = subcon

class Value(Construct):
    """
    Embeds a computed-value into the parsed obejct. The value is computed by
    a function, and this construct does not does read or write anything to the
    stream.
    
    Constructor:
    * name - the name of the construct
    * func - a function taking the context as an argument; returns the computed
      value to use.
    
    Example:
    Value("blah", lambda ctx: 17)
    """
    __slots__ = ["func"]
    def __init__(self, name, func):
        Construct.__init__(self, name)
        self.func = func
    def _build(self, obj, stream, context):
        context["_"][self.name] = self.func(context["_"])
    def _parse(self, stream, context):
        return self.func(context["_"])
    def _getsize(self, context):
        return 0

class Anchor(Construct):
    """
    Reports the stream position where this construct was called; does not
    read or write anything to the stream;
    
    Constructor:
    * name - the name of the construct
    
    Example:
    Anchor("blah")
    """
    __slots__ = []
    def _parse(self, stream, context):
        return stream.tell()
    def _build(self, obj, stream, context):
        context["_"][self.name] = stream.tell()
    def _getsize(self, context):
        return 0


















